// INCLUDES --------------------------------------------------------------------
#include "Framebuffer.h"

// DEFINES ---------------------------------------------------------------------
#define MAX_BUFFER 1000                 // max amount of concurrent framebuffers

// IMAGE FILE HEADERS ----------------------------------------------------------
#define PPMv6 "P6\n\n%i %i\n255\n"      // PPM v6 -> supports: RGB8

// FUNCTIONS -------------------------------------------------------------------

static bool        initialized = false; // init flag for the entire framebuffer engine
static ui32        currentBuffer = 0;   // current amount of active framebuffers
static Framebuffer raster[MAX_BUFFER];  // actual stack of framebuffers

// check for a valid set up framebuffer that can be used internally
bool FramebufferCheckValidity(ui32 slot)
{
  return (bool)(slot < currentBuffer &&        // slot has been FramebufferCreate()'ed AND
                raster[slot].buffer != NULL && // memory was successfully allocated AND
                raster[slot].width > 0 &&      // width is at least one pixel AND
                raster[slot].height > 0);      // height is at least one pixel
};

// clear all memory and reset framebuffer engine
void FramebufferInit()
{
  // reset framebuffer data
  i32 i;
  for (i = 0; i < MAX_BUFFER; i++)
  {
    // free used memory
    if (raster[i].buffer != NULL) free(raster[i].buffer); 
    
    // reset buffer/callback addresses
    raster[i].buffer = NULL;    
    raster[i].cb     = NULL; 
    
    // set width/height=0
    raster[i].width = raster[i].height = 0;
  }
  currentBuffer = 0;  // reset reservation counter
  initialized = true; // set init-ready flag
};

// create a framebuffer held in memory -> RETURNS CREATED SLOT NUMBER
i32 FramebufferCreate(ui32 width, ui32 height)
{
  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  // check for an available framebuffer slot
  if (currentBuffer < MAX_BUFFER)
  {
    // set infos up in slot X and allocate memory
    
    // allocate memory
    raster[currentBuffer].buffer = (Vector4*)calloc(width*height, sizeof(Vector4));

    // if allocating of memory succeeded:
    if (raster[currentBuffer].buffer != NULL)
    {
      // clear framebuffer to [r:0,g:0,b:0,a:1]
      Vector4Clear(raster[currentBuffer].buffer, width*height);

      // save width/height of buffer
      raster[currentBuffer].width = width;
      raster[currentBuffer].height = height;

      // return correct slot number
      return currentBuffer++;
    }
  }
  return -1;
};

// register a function callback for FramebufferProcess() to work with
void FramebufferRegister(ui32 slot, FramebufferCallback cb)
{
  GUARD_NULL("FramebufferRegister *cb", cb);

  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  // check for a valid buffer slot, otherwise set nothing
  if (slot < currentBuffer) raster[slot].cb = cb;
};

// process the framebuffer and call a registered callback
void FramebufferProcess(ui32 slot, ui32 ProcessingType)
{
  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  // only process a valid framebuffer!
  if (FramebufferCheckValidity(slot) == false || // check standard setup
      raster[slot].cb == NULL) return;           // require a correct callback

  // TODO: add framebuffer processing for BLOCK
  switch(ProcessingType)
  {
  case LINE: // [for every line -> for every pixel of that line], top-to-bottom
  {
    i32 line, pixel, width = raster[slot].width;
    for (line = 0; line < raster[slot].height; line++)
      for (pixel = 0; pixel < raster[slot].width; pixel++)
      {
        // execute callback with x,y,color
        raster[slot].cb(pixel, line, &raster[slot].buffer[line*width+pixel]);
      }    
    break;
  }
  
  case BLOCK:
  {  
    break;
  }
  }
};    

// clear framebuffer to color X -> Vector4 represents RGBA (x=red,y=green,z=blue,w=alpha)
void FramebufferClear(ui32 slot, Vector4 *c)
{
  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  // only clear a valid framebuffer!
  if (FramebufferCheckValidity(slot) == false) return;
  
  // calculate pixel amount in framebuffer
  i32 pixelAmount = raster[slot].width * raster[slot].height;

  /* TODO: _if_ there is a better way to copy a color over the entire buffer in a single go,
           it could be used to also swipe the buffer to black when regularly cleaning
  */

  // check if optional color is given
  if (c != NULL) Vector4Copy(raster[slot].buffer, c, (ui32)pixelAmount); // yep, copy given color onto every pixel in the buffer
  else Vector4Clear(raster[slot].buffer, (ui32)pixelAmount);             // nope, clear entire buffer to r,g,b,a=[0,0,0,1]
};

// write framebuffer to disk as RGB8 PPM
void FramebufferWrite(ui32 slot, fp gamma, ui8 *filename)
{
  GUARD_NULL("FramebufferWrite *filename", filename);

  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  // only clear a valid framebuffer!
  if (FramebufferCheckValidity(slot) == false) return;

  // pointer to our new PPM image
  FILE *file = NULL;

  // create a proper filename
  STRING_GEN("%s.ppm", filename); 
  if (stringGenSuccess == -1) return;

  // open up a new image file in "overwrite" mode
  file = fopen(stringGen, "wb");
  GUARD_NULL("FramebufferWrite *file", file);

  // assemble file header
  STRING_GEN(PPMv6, raster[slot].width, raster[slot].height);
  if (stringGenSuccess == -1) return;

  // write the file header
  i32 ret = fprintf(file, "%s", stringGen);
  if (ret < 0) return;

  // write pixel data to disk
  i32 pixelAmount = raster->width * raster->height;   // calculate amount of pixels

  // create a temporary RGB8 pixel
  ColorRGB8 color;
  memset(&color, 0, sizeof(ColorRGB8));

  // write out pixel data
  i32 i, c;
  for (i = 0; i < pixelAmount; i++)
  {
    // TODO: incorporate gamma correction
    for (c = 0; c < 3; c++)
      color.rgb[c] = (ui8)raster[slot].buffer[i].v[c]; // convert components from float->byte
    fwrite(&color, sizeof(ColorRGB8), 1, file);        // write pixel to disk
  }
  // close file
  fclose(file);
}; 

// retrieve raw access to a framebuffer that was previously created
Framebuffer* FramebufferGetRaw(ui32 slot)
{
  // if the framebuffer sys hasn't been initialized at least once, do so
  if (initialized == false) FramebufferInit();

  if (slot < currentBuffer) return &raster[slot]; // valid slot? if yes, return the framebuffer pointer
  return NULL;                                    // else return NULL as error
};
